package org.fox.beetl.ext.spring.security;

import java.io.IOException;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.beetl.core.Context;
import org.beetl.core.Tag;
import org.fox.beetl.ext.spring.utils.Utils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.GenericTypeResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;

/**
 * Spring Security &lt;authorize access="..."&gt;标签实现
 *
 * @author Chen Rui
 */
public class AccessExpressionIfTag extends Tag implements ApplicationContextAware {
	/* ----- ----- ----- ----- IOC属性 ----- ----- ----- ----- */
	/**
	 * Spring应用程序上下文
	 */
	private ApplicationContext applicationContext = null;

	/**
	 * Spring应用程序上下文
	 *
	 * @param applicationContext
	 */
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}

	/* ----- ----- ----- ----- 其他方法 ----- ----- ----- ----- */
	/**
	 * 标签渲染类
	 */
	@Override
	public void render() {
		// 参数异常
		if ((args.length == 0) || (args[0] == null) || (!(args[0] instanceof String))) {
			throw new IllegalArgumentException("tag需要一个String表示的安全表达式");
		}
		String access = (String) args[0];
		boolean authorized = false;

		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			// 如果上下文中无用户的凭证，认证失败
			authorized = false;
		} else {
			// 否则执行权限表达式进行认证
			SecurityExpressionHandler<FilterInvocation> expressionHandler = getExpressionHandler();
			Expression accessExpression = expressionHandler.getExpressionParser().parseExpression(access);

			authorized = ExpressionUtils.evaluateAsBoolean(accessExpression,
					createExpressionEvaluationContext(expressionHandler, ctx));
		}

		// 认证成功，执行标签体
		if (authorized) {
			doBodyRender();
		}
	}

	/**
	 * 获取安全表达式处理器<br>
	 * 本方法参照Spring Security源码修改
	 *
	 * @return
	 */
	@SuppressWarnings({ "unchecked" })
	private SecurityExpressionHandler<FilterInvocation> getExpressionHandler() {
		@SuppressWarnings("rawtypes")
		Map<String, SecurityExpressionHandler> securityExpressionHandlers = applicationContext
				.getBeansOfType(SecurityExpressionHandler.class);

		for (SecurityExpressionHandler<?> securityExpressionHandler : securityExpressionHandlers.values()) {
			if (FilterInvocation.class.equals(GenericTypeResolver.resolveTypeArgument(
					securityExpressionHandler.getClass(), SecurityExpressionHandler.class))) {
				return (SecurityExpressionHandler<FilterInvocation>) securityExpressionHandler;
			}
		}

		throw new IllegalStateException("Spring上下文中不存在有效的WebSecurityExpressionHandler实例，请检查Spring Security配置，启用安全表达式支持");
	}

	/**
	 * 获取安全表达式执行环境<br>
	 * 本方法参照Spring Security源码修改
	 *
	 * @param handler
	 * @param context
	 * @return
	 */
	private EvaluationContext createExpressionEvaluationContext(SecurityExpressionHandler<FilterInvocation> handler,
			Context context) {
		FilterInvocation filterInvocation = new FilterInvocation(Utils.getRequest(context), Utils.getResponse(context),
				new FilterChain() {
					@Override
					public void doFilter(ServletRequest request, ServletResponse response) throws IOException,
							ServletException {
						throw new UnsupportedOperationException();
					}
				});

		return handler
				.createEvaluationContext(SecurityContextHolder.getContext().getAuthentication(), filterInvocation);
	}
}
